<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="eLbn3" id="eLbn3"><span data-lake-id="u6803c560" id="u6803c560">典型回答</span></h1>
  <p data-lake-id="ufef628c3" id="ufef628c3"><br></p>
  <p data-lake-id="ub85ad480" id="ub85ad480"><span data-lake-id="u035b834b" id="u035b834b">编程语言（Programming Language）分为低级语言（Low-level Language）和高级语言（High-level Language）。</span></p>
  <p data-lake-id="uf96594f8" id="uf96594f8"><span data-lake-id="ue4b4bace" id="ue4b4bace">​</span><br></p>
  <p data-lake-id="u512cbe47" id="u512cbe47"><span data-lake-id="u236cd383" id="u236cd383">机器语言（Machine Language）和汇编语言（Assembly Language）属于低级语言，直接用计算机指令编写程序。</span></p>
  <p data-lake-id="u9806d7aa" id="u9806d7aa"><span data-lake-id="ubf59dd60" id="ubf59dd60">​</span><br></p>
  <p data-lake-id="ue564a340" id="ue564a340"><span data-lake-id="u5cd4b5e5" id="u5cd4b5e5">而C、C++、Java、Python等属于高级语言。</span><strong><span data-lake-id="u964c0f93" id="u964c0f93">低级语言是计算机认识的语言、高级语言是程序员认识的语言。</span></strong></p>
  <p data-lake-id="uc91f2275" id="uc91f2275"><span data-lake-id="uad2e566c" id="uad2e566c">​</span><br></p>
  <p data-lake-id="u562b9c89" id="u562b9c89"><strong><span data-lake-id="ua114cdf3" id="ua114cdf3">将便于人编写、阅读、维护的高级计算机语言所写作的源代码程序，翻译为计算机能解读、运行的低阶机器语言的程序的过程就是编译</span></strong><span data-lake-id="u25029e10" id="u25029e10">。负责这一过程的处理的工具叫做编译器。</span></p>
  <p data-lake-id="u89c31dc1" id="u89c31dc1"><span data-lake-id="u962362ca" id="u962362ca">​</span><br></p>
  <p data-lake-id="u1912d553" id="u1912d553"><span data-lake-id="ua8a5e69b" id="ua8a5e69b">我们可以通过javac命令将Java程序的源代码编译成Java字节码，即我们常说的class文件。这是我们通常意义上理解的编译。但是，字节码并不是机器语言，要想让机器能够执行，还需要把字节码翻译成机器指令。这个过程是Java虚拟机做的，这个过程也叫编译。是更深层次的编译。</span></p>
  <p data-lake-id="ue8ce3760" id="ue8ce3760"><span data-lake-id="u771402e3" id="u771402e3">​</span><br></p>
  <p data-lake-id="uc8ce1104" id="uc8ce1104"><span data-lake-id="uf8059935" id="uf8059935">在编译原理中，把源代码翻译成机器指令，一般要经过以下几个重要步骤：</span></p>
  <p data-lake-id="u96787a3f" id="u96787a3f"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1694948851030-4fc1dc4d-feb0-49a7-a6dd-3878b1f252c2.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_23%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u8c813452" id="u8c813452"><br></p>
  <p data-lake-id="u825b401f" id="u825b401f"><strong><span data-lake-id="ua51d5664" id="ua51d5664">我们可以把将.java文件编译成.class的编译过程称之为前端编译。把将.class文件翻译成机器指令的编译过程称之为后端编译。</span></strong></p>
  <p data-lake-id="ufa6e3277" id="ufa6e3277"><br></p>
  <p data-lake-id="u0f831afc" id="u0f831afc"><strong><span data-lake-id="u291f6f1f" id="u291f6f1f">反编译的过程与编译刚好相反，就是将已编译好的编程语言还原到未编译的状态，也就是找出程序语言的源代码。</span></strong><span data-lake-id="uf5e2c2e4" id="uf5e2c2e4">就是将机器看得懂的语言转换成程序员可以看得懂的语言。Java语言中的反编译一般指将class文件转换成java文件。</span></p>
  <p data-lake-id="u2c7fb5c4" id="u2c7fb5c4"><span data-lake-id="u5870070f" id="u5870070f">​</span><br></p>
  <h1 data-lake-id="JjAeI" id="JjAeI"><span data-lake-id="u463dca0d" id="u463dca0d">扩展知识</span></h1>
  <h2 data-lake-id="wRvY1" id="wRvY1"><span data-lake-id="uf6e05832" id="uf6e05832">反编译工具</span></h2>
  <p data-lake-id="ub7da5fa2" id="ub7da5fa2"><br></p>
  <p data-lake-id="u0e74bb47" id="u0e74bb47"><span data-lake-id="u79d9a952" id="u79d9a952">介绍3个Java的反编译工具：javap、jad和cfr</span></p>
  <h3 data-lake-id="wWYdM" id="wWYdM"><span data-lake-id="uceeef361" id="uceeef361">javap</span></h3>
  <p data-lake-id="ud20c01a6" id="ud20c01a6"><br></p>
  <h3 data-lake-id="Is4dv" id="Is4dv"><span data-lake-id="u401fc3c9" id="u401fc3c9">jad</span></h3>
  <p data-lake-id="u3fecbbce" id="u3fecbbce"><span data-lake-id="u873cc7d4" id="u873cc7d4">jad是一个比较不错的反编译工具，只要下载一个执行工具，就可以实现对</span><span data-lake-id="u6572b388" id="u6572b388">class</span><span data-lake-id="uaf819199" id="uaf819199">文件的反编译了。还是上面的源代码，使用jad反编译后内容如下：</span></p>
  <p data-lake-id="u4e752ca8" id="u4e752ca8"><span data-lake-id="ud6af08c3" id="ud6af08c3">命令：</span><span data-lake-id="u3b87df15" id="u3b87df15">jad switchDemoString.class</span></p>
  <pre lang="java"><code>
public class switchDemoString
{
    public switchDemoString()
    {
    }
    public static void main(String args[])
    {
        String str = "world";
        String s;
        switch((s = str).hashCode())
        {
        default:
            break;
        case 99162322:
            if(s.equals("hello"))
                System.out.println("hello");
            break;
        case 113318802:
            if(s.equals("world"))
                System.out.println("world");
            break;
        }
    }
}
</code></pre>
  <p data-lake-id="ucbaf13ee" id="ucbaf13ee"><span data-lake-id="u6645083a" id="u6645083a">看，这个代码你肯定看的懂，因为这不就是标准的java的源代码么。这个就很清楚的可以看到原来</span><span data-lake-id="u1285fdee" id="u1285fdee">字符串的switch是通过</span><span data-lake-id="ub54bc9c3" id="ub54bc9c3">equals()</span><span data-lake-id="u0d63c009" id="u0d63c009">和</span><span data-lake-id="u9995ebac" id="u9995ebac">hashCode()</span><span data-lake-id="ufee8f4ec" id="ufee8f4ec">方法来实现的</span><span data-lake-id="ud6a654df" id="ud6a654df">。</span></p>
  <p data-lake-id="ue5d101cb" id="ue5d101cb"><span data-lake-id="u90edb838" id="u90edb838">但是，jad已经很久不更新了，在对Java7生成的字节码进行反编译时，偶尔会出现不支持的问题，在对Java 8的lambda表达式反编译时就彻底失败。</span></p>
  <h3 data-lake-id="UBz41" id="UBz41"><span data-lake-id="ud52bdbca" id="ud52bdbca">CFR</span></h3>
  <p data-lake-id="ufe6084bd" id="ufe6084bd"><span data-lake-id="uef0b6e64" id="uef0b6e64">jad很好用，但是无奈的是很久没更新了，所以只能用一款新的工具替代他，CFR是一个不错的选择，相比jad来说，他的语法可能会稍微复杂一些，但是好在他可以work。</span></p>
  <p data-lake-id="u8bac9b5f" id="u8bac9b5f"><span data-lake-id="u8cc57a28" id="u8cc57a28">如，我们使用cfr对刚刚的代码进行反编译。执行一下命令：</span></p>
  <pre lang="java"><code>
java -jar cfr_0_125.jar switchDemoString.class --decodestringswitch false
</code></pre>
  <p data-lake-id="uf51bf170" id="uf51bf170"><span data-lake-id="ud6cd5a05" id="ud6cd5a05">得到以下代码：</span></p>
  <pre lang="java"><code>
public class switchDemoString {
    public static void main(String[] arrstring) {
        String string;
        String string2 = string = "world";
        int n = -1;
        switch (string2.hashCode()) {
            case 99162322: {
                if (!string2.equals("hello")) break;
                n = 0;
                break;
            }
            case 113318802: {
                if (!string2.equals("world")) break;
                n = 1;
            }
        }
        switch (n) {
            case 0: {
                System.out.println("hello");
                break;
            }
            case 1: {
                System.out.println("world");
                break;
            }
        }
    }
}
</code></pre>
  <p data-lake-id="u4c59b6ce" id="u4c59b6ce"><span data-lake-id="u64efd8ef" id="u64efd8ef">通过这段代码也能得到字符串的switch是通过</span><span data-lake-id="u5a774004" id="u5a774004">equals()</span><span data-lake-id="u593793d8" id="u593793d8">和</span><span data-lake-id="u42c7dda8" id="u42c7dda8">hashCode()</span><span data-lake-id="ua9afa698" id="ua9afa698">方法来实现的结论。</span></p>
  <p data-lake-id="u0f851861" id="u0f851861"><span data-lake-id="uc6aa43c9" id="uc6aa43c9">相比Jad来说，CFR有很多参数，还是刚刚的代码，如果我们使用以下命令，输出结果就会不同：</span></p>
  <pre lang="java"><code>
java -jar cfr_0_125.jar switchDemoString.class

public class switchDemoString {
    public static void main(String[] arrstring) {
        String string;
        switch (string = "world") {
            case "hello": {
                System.out.println("hello");
                break;
            }
            case "world": {
                System.out.println("world");
                break;
            }
        }
    }
}
</code></pre>
  <p data-lake-id="ueb17752f" id="ueb17752f"><span data-lake-id="u63fbb0fd" id="u63fbb0fd">所以</span><span data-lake-id="u9319523e" id="u9319523e">--decodestringswitch</span><span data-lake-id="uec4658fc" id="uec4658fc">表示对于switch支持string的细节进行解码。类似的还有</span><span data-lake-id="u51f92956" id="u51f92956">--decodeenumswitch</span><span data-lake-id="udd73d26a" id="udd73d26a">、</span><span data-lake-id="u0a8a50a2" id="u0a8a50a2">--decodefinally</span><span data-lake-id="u6a0d67c2" id="u6a0d67c2">、</span><span data-lake-id="u50642eed" id="u50642eed">--decodelambdas</span><span data-lake-id="u0511dc16" id="u0511dc16">等。在我的关于语法糖的文章中，我使用</span><span data-lake-id="uedf6c1f8" id="uedf6c1f8">--decodelambdas</span><span data-lake-id="u73f8d544" id="u73f8d544">对lambda表达式警进行了反编译。 源码：</span></p>
  <pre lang="java"><code>
public static void main(String... args) {
    List&lt;String&gt; strList = ImmutableList.of("Hollis", "公众号：Hollis", "博客：www.hollischuang.com");

    strList.forEach( s -&gt; { System.out.println(s); } );
}
</code></pre>
  <p data-lake-id="u11411dea" id="u11411dea"><span data-lake-id="ue9047559" id="ue9047559">java -jar cfr_0_125.jar lambdaDemo.class --decodelambdas false</span><span data-lake-id="u929ea018" id="u929ea018">反编译后代码：</span></p>
  <pre lang="java"><code>
public static /* varargs */ void main(String ... args) {
    ImmutableList strList = ImmutableList.of((Object)"Hollis", (Object)"\u516c\u4f17\u53f7\uff1aHollis", (Object)"\u535a\u5ba2\uff1awww.hollischuang.com");
    strList.forEach((Consumer&lt;String&gt;)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$0(java.lang.String ), (Ljava/lang/String;)V)());
}

private static /* synthetic */ void lambda$main$0(String s) {
    System.out.println(s);
}
</code></pre>
  <p data-lake-id="u5e2ecd69" id="u5e2ecd69"><span data-lake-id="u83858427" id="u83858427">CFR还有很多其他参数，均用于不同场景，读者可以使用java -jar cfr_0_125.jar --help进行了解。这里不逐一介绍了。</span></p>
  <p data-lake-id="ufcb962d6" id="ufcb962d6"><br></p>
  <h2 data-lake-id="cDFdc" id="cDFdc"><span data-lake-id="udfccc675" id="udfccc675">如何防止反编译</span></h2>
  <p data-lake-id="uef3d58ec" id="uef3d58ec"><span data-lake-id="u7d43a544" id="u7d43a544">由于我们有工具可以对</span><span data-lake-id="uf78d0f77" id="uf78d0f77">Class</span><span data-lake-id="u67d3e89d" id="u67d3e89d">文件进行反编译，所以，对开发人员来说，如何保护Java程序就变成了一个非常重要的挑战。但是，魔高一尺、道高一丈。当然有对应的技术可以应对反编译咯。但是，这里还是要说明一点，和网络安全的防护一样，无论做出多少努力，其实都只是提高攻击者的成本而已。无法彻底防治。</span></p>
  <p data-lake-id="u3a72de9e" id="u3a72de9e"><span data-lake-id="u35df6c5d" id="u35df6c5d">典型的应对策略有以下几种：</span></p>
  <ul list="u9517552a">
   <li fid="udc6330a9" data-lake-id="u8c16719b" id="u8c16719b"><span data-lake-id="u4ef9e13a" id="u4ef9e13a">隔离Java程序</span></li>
  </ul>
  <ul list="u9517552a" data-lake-indent="1">
   <li fid="u61601ec5" data-lake-id="u380b6ee5" id="u380b6ee5"><span data-lake-id="ub02e19df" id="ub02e19df">让用户接触不到你的Class文件</span></li>
  </ul>
  <ul list="u9517552a" start="2">
   <li fid="udc6330a9" data-lake-id="ubd686328" id="ubd686328"><span data-lake-id="u0e470eee" id="u0e470eee">对Class文件进行加密</span></li>
  </ul>
  <ul list="u9517552a" data-lake-indent="1">
   <li fid="ua1bb2dfe" data-lake-id="u3da5fed9" id="u3da5fed9"><span data-lake-id="u74798813" id="u74798813">提到破解难度</span></li>
  </ul>
  <ul list="u9517552a" start="3">
   <li fid="udc6330a9" data-lake-id="u515fc775" id="u515fc775"><span data-lake-id="u72beca0f" id="u72beca0f">代码混淆</span></li>
  </ul>
  <ul list="u9517552a" data-lake-indent="1">
   <li fid="u05fd10be" data-lake-id="u1d0cdfe4" id="u1d0cdfe4"><span data-lake-id="u46d58855" id="u46d58855">将代码转换成功能上等价，但是难于阅读和理解的形式</span></li>
  </ul>
 </body>
</html>